/*
 * Opus音频播放器
 * 该程序用于播放使用Opus编码压缩的音频文件
 */

#include <alsa/asoundlib.h>  // ALSA音频库，用于Linux音频设备操作
#include <opus/opus.h>       // Opus编解码器库

#define IFILE "record.opus"  // 要播放的音频文件名

/*
 * 播放器结构体
 * 包含播放所需的所有信息和资源
 */
struct player {
    snd_pcm_t* device;       // ALSA PCM设备句柄
    int channels;            // 音频声道数
    int sample_rate;         // 采样率
    int duration;            // 周期持续时间（毫秒）
    int frames;              // 每个周期的帧数
    OpusDecoder* decoder;    // Opus解码器实例
    opus_int16* buffer;      // 音频数据缓冲区
};

/*
 * 创建播放器实例
 * @param name: 设备名称
 * @param sample_rate: 采样率
 * @param channels: 声道数
 * @param duration: 周期持续时间（毫秒）
 * @return: 成功返回player指针，失败返回NULL
 */
struct player* player_create(const char* name, int sample_rate, int channels, int duration)
{
    // 分配播放器结构体内存
    struct player* player = malloc(sizeof(struct player));
    if (!player)
    {
        return NULL;
    }

    // 初始化基本参数
    player->channels = channels;
    player->sample_rate = sample_rate;
    player->duration = duration;

    // 打开PCM播放设备
    int error = snd_pcm_open(&player->device, name, SND_PCM_STREAM_PLAYBACK, 0);
    if (error < 0)
    {
        fprintf(stderr, "打开设备 %s 失败: %s\n", name, snd_strerror(error));
        free(player);
        return NULL;
    }

    // 设置PCM参数
    error = snd_pcm_set_params(player->device,
                            SND_PCM_FORMAT_S16_LE,          // 16位小端格式
                            SND_PCM_ACCESS_RW_INTERLEAVED,  // 交错访问模式
                            player->channels,               // 声道数
                            player->sample_rate,            // 采样率
                            1,                              // 是否允许软件重采样
                            player->duration * 1000);       // 延迟时间（纳秒）
    if (error < 0) {
        fprintf(stderr, "设置PCM参数失败: %s\n", snd_strerror(error));
        snd_pcm_close(player->device);
        free(player);
        return NULL;
    }

    // 创建Opus解码器
    player->decoder = opus_decoder_create(player->sample_rate, player->channels, &error);
    if (error != OPUS_OK) {
        fprintf(stderr, "创建Opus解码器失败: %s\n", opus_strerror(error));
        snd_pcm_close(player->device);
        free(player);
        return NULL;
    }

    // 计算每周期帧数并分配缓冲区
    player->frames = player->sample_rate * player->duration / 1000;
    player->buffer = malloc(snd_pcm_frames_to_bytes(player->device, player->frames));
    if (!player->buffer) {
        opus_decoder_destroy(player->decoder);
        snd_pcm_close(player->device);
        free(player);
        return NULL;
    }

    return player;
}

/*
 * 销毁播放器实例并释放资源
 * @param player: 要销毁的播放器实例
 */
void player_destroy(struct player *player)
{
    if (player) {
        // 释放缓冲区内存
        if (player->buffer) {
            free(player->buffer);
            player->buffer = NULL;
        }
        // 关闭PCM设备
        if (player->device) {
            snd_pcm_close(player->device);
            player->device = NULL;
        }
        // 销毁Opus解码器
        if (player->decoder) {
            opus_decoder_destroy(player->decoder);
            player->decoder = NULL;
        }
        // 释放播放器结构体
        free(player);
    }
}

/*
 * 启动播放器
 * @param player: 播放器实例
 * @return: 成功返回0，失败返回负值
 */
int player_start(struct player *player)
{
    if (!player)
    {
        return -1;
    }

    return snd_pcm_start(player->device);
}

/*
 * 停止播放器
 * @param player: 播放器实例
 * @return: 成功返回0，失败返回负值
 */
int player_stop(struct player *player)
{
    if (!player)
    {
        return -1;
    }

    return snd_pcm_drain(player->device);
}

/*
 * 播放音频数据
 * @param player: 播放器实例
 * @param data: Opus编码的音频数据
 * @param size: 数据大小
 * @return: 成功返回0，失败返回负值
 */
int play_audio(struct player* player, char* data, size_t size)
{
    if (!player)
    {
        return -1;
    }

    // 使用Opus解码器解码音频数据
    int dec_frames = opus_decode(player->decoder, data, size, player->buffer, player->frames, 0);
    if (dec_frames < 0) {
        fprintf(stderr, "Opus解码失败: %s\n", opus_strerror(dec_frames));
        return -1;
    }

    // 通过PCM设备播放解码后的音频数据
    snd_pcm_sframes_t frames = snd_pcm_writei(player->device, player->buffer, dec_frames);
    if (frames < 0) {
        fprintf(stderr, "PCM写入失败: %s\n", snd_strerror(frames));
        return -1;
    }

    return 0;
}

// 文件读取缓冲区
char file_buffer[4096];

int main()
{
    // 打开要播放的Opus音频文件
    FILE *fp = fopen(IFILE, "rb");
    if (!fp)
    {
        perror(IFILE);
        return EXIT_FAILURE;
    }

    // 创建播放器实例
    struct player* player = player_create("default", 16000, 1, 20);
    if (!player)
    {
        fclose(fp);
        return EXIT_FAILURE;
    }

    // 启动播放器
    player_start(player);

    // 循环读取并播放音频数据
    while (1) {
        int32_t rbytes;
        // 读取帧头（数据块大小）
        if (fread(&rbytes, 1, sizeof(rbytes), fp) != sizeof(rbytes))
        {
            if (feof(fp)) 
            {
                puts("播放完成");
            }
            else
            {
                perror("读取文件失败");
            }
            break;
        }

        // 检查数据块大小是否超过缓冲区容量
        if (rbytes > sizeof(file_buffer))
        {
            fprintf(stderr, "帧大小 %d 过大\n", rbytes);
            break;
        }

        // 读取实际的音频数据
        size_t bytes = fread(file_buffer, 1, rbytes, fp);
        if (bytes != rbytes) {
            fprintf(stderr, "期望读取 %d 字节，实际读取 %d 字节\n", rbytes, bytes);
            break;
        }

        printf("读取 %d 字节\n", bytes);

        // 播放音频数据
        if (play_audio(player, file_buffer, bytes) < 0)
        {
            continue;
        }
    }

    // 停止播放并清理资源
    player_stop(player);
    player_destroy(player);
    fclose(fp);

    return EXIT_SUCCESS;
}